package com.lang.thread;

import com.lang.object.A;
import org.junit.Test;

import java.lang.reflect.Field;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;

/**

 * 子线程 InheritableThreadLocal
 * Created by 85243 on 2017/4/7.
 */
public class ThreadLocalDemo1 {
    public static ThreadLocal STATIC_TL = new ThreadLocal();

    /**
     * 静态ThreadLocal与线程私有的ThreadLocal对象模型区别
     * 此虚拟机中只有一个 STATIC_TL对象，该对象作为key存在于Thread1和Thread2线程中的 threadLocals<ThreadLocal.ThreadLocalMap> 属性中，当thread1调用STATIC_TL的get时实际上调用的是 thread1.threadLocals.get(STATIC_TL)
     */
    @Test
    public void staticAndThreadHeapMode() throws InterruptedException {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
               String str = "线程名称："+ Thread.currentThread().getName() + "；" + "线程id："+Thread.currentThread().getId()+";\n";
                STATIC_TL.set("静态变量对应的线程ID："+Thread.currentThread().getId());
                ThreadLocal selfThreadLocal = new ThreadLocal();
                selfThreadLocal.set("临时变量对应的线程ID："+Thread.currentThread().getId());
                try {
                    Thread.sleep(1000L);
                    Field threadLocals = Thread.class.getDeclaredField("threadLocals");
                    threadLocals.setAccessible(true);
                    Object o = threadLocals.get(Thread.currentThread());
                    System.out.println(str
                            + "静态ThreadLocal对象地址:"+ STATIC_TL +";静态ThreadLocal对应的值："+ STATIC_TL.get()+";\n"
                            + "线程ThreadLocal对象地址:"+ selfThreadLocal +";线程ThreadLocal对应的值："+ selfThreadLocal.get()+";\n"  +""+"当前线程中的ThreadLocalMap:"+o.toString());

                } catch (NoSuchFieldException | IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

        Thread thread1 = new Thread(runnable,"thread1");
        Thread thread2 = new Thread(runnable,"thread2");
        thread1.start();
        thread2.start();
        Thread.sleep(2000L);

    }


    /**
     * ThreadLocal发生内存泄漏，下面代码中，当threadLocal=null后，此时ThreadLocal对象只有一个弱引用。关注o.table中，当发生gc后，有个Entry对应的referent=null但是还引用了value，导致实际value未被回收，直到线程结束后才会被回收，若是线程池则发生内存泄漏，可以使用remove方法降低内存泄漏。
     */
    @Test
    public void testOOM() throws NoSuchFieldException, IllegalAccessException {
        ThreadLocal<List> threadLocal = new ThreadLocal<>();
        List list = new ArrayList();
        list.add("sss");
        threadLocal.set(list);

        Field threadLocals = Thread.class.getDeclaredField("threadLocals");
        threadLocals.setAccessible(true);
        Object o = threadLocals.get(Thread.currentThread());
        threadLocal = null;//将threadLocal对应的对象强引用去除
        list = null;//将list强引用也去掉，虽然list非弱引用
        System.gc();//在这里打断点，观察o.table中的数据
        System.out.println(o);
    }

    public static ExecutorService THREAD_POOL = Executors.newFixedThreadPool(10);
    static SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    static ThreadLocal<SimpleDateFormat> DATE_FORMAT_TL = new ThreadLocal<>();
    static {
        DATE_FORMAT_TL.set( new SimpleDateFormat("yyyy-MM-dd HH:mm:ss") );
    }

    /**
     * ThreadLocal使用场景
     * @throws InterruptedException
     */
    @Test
    public void usageSimpleDateFormat() throws InterruptedException {
        int threadNum = 1000;
        System.out.println("直接使用全局变量，会出现线程安全问题,理论上出现的时间间隔都为1s");
        Long startTime = System.nanoTime();
        CountDownLatch countDownLatch = new CountDownLatch(threadNum);
        for (int i = 0; i < threadNum; i++) {
            int finalI = i;
            THREAD_POOL.submit(() -> {
                Date date = new Date(1000 * finalI);
//                System.out.println(DATE_FORMAT.format(date));
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        System.out.println("全局变量使用时间："+(System.nanoTime()-startTime));
        System.out.println();

        System.out.println("直接使用局部变量，会出现时间消耗过大问题");
        startTime = System.nanoTime();
        CountDownLatch countDownLatch1 = new CountDownLatch(threadNum);
        for (int i = 0; i < threadNum; i++) {
            int finalI = i;
            THREAD_POOL.submit(() -> {
                Date date = new Date(1000 * finalI);
                SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//                System.out.println(simpleDateFormat.format(date));
                countDownLatch1.countDown();
            });
        }
        countDownLatch1.await();
        System.out.println("局部变量使用时间："+(System.nanoTime()-startTime));
        System.out.println();

        System.out.println("使用ThreadLocal变量，会出现时间消耗过大问题");
        startTime = System.nanoTime();
        CountDownLatch countDownLatch2 = new CountDownLatch(threadNum);
        for (int i = 0; i < threadNum; i++) {
            int finalI = i;
            THREAD_POOL.submit(() -> {
                Date date = new Date(1000 * finalI);
//                                System.out.println(DATE_FORMAT_TL.get().format(date));
                countDownLatch2.countDown();
            });
        }
        countDownLatch2.await();
        System.out.println("ThreadLocal变量使用时间："+(System.nanoTime()-startTime));

    }

    /**
     * 父线程拥有 InheritableThreadLocal 时， 当创建子线程时将父线程的Thread.inheritableThreadLocals 复制到子线程的Thread.inheritableThreadLocals中，子线程可以使用InheritableThreadLocal读取父线程中的数据，但是父子线程各自拥有自己的InheritableThreadLocal副本，所以子线程修改 inheritableThreadLocal变量父线程不受影响
     */
    static ThreadLocal threadLocal = new ThreadLocal();
    static InheritableThreadLocal inheritableThreadLocal = new InheritableThreadLocal();
    @Test
    public void testInheritableThreadLocal(){
        //当子线程修改值后查看主线程数据
        AtomicBoolean startMainThrad = new AtomicBoolean();
        inheritableThreadLocal.set("主线程设置的继承数据");
        threadLocal.set("主线程设置的数据");
        System.out.println(Thread.currentThread().getName()+"父线程获取环境变量inheritableThreadLocal:"+inheritableThreadLocal.get());

        Thread thread = new Thread(()->{
            System.out.println(Thread.currentThread().getName()+"子线程获取环境变量inheritableThreadLocal："+inheritableThreadLocal.get());
            System.out.println(Thread.currentThread().getName()+"子线程获取环境变量threadLocal："+threadLocal.get());

            inheritableThreadLocal.set("子线程修改了父线程的inheritableThreadLocal，查看父线程是否会产生影响");
            System.out.println(Thread.currentThread().getName()+"子线程修改了父线程的inheritableThreadLocal，查看父线程是否会产生影响");
            startMainThrad.set(true);
        },"child");

        thread.setDaemon(true);//设置为守护线程，以防threadLocal变为空
        thread.start();

        //阻塞直到子线程修改了数据
        while(!startMainThrad.get()){

        }

        System.out.println(Thread.currentThread().getName()+"线程 当子线程修改数据后，父线程获取环境变量inheritableThreadLocal:"+inheritableThreadLocal.get());

    }

}


